/*
 * Copyright (c) 2007 jMouse Gestures
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMouse Gestures' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jmg.gesture;


/**
 * GestureFactory class contains a bunch of handy methods for creating
 * <code>Gestures</code>.
 *
 * @author  Christopher Martin
 * @version $Id: GestureFactory.java 9 2007-05-14 09:26:27Z GentlemanHal $
 */
public class GestureFactory {
    /** Angle in radians that represents North (Up).              */
    public static final float ANGLE_NORTH      = GestureMovement.ANGLE_NORTH;
    /** Angle in radians that represents North-East (Up-Right).   */
    public static final float ANGLE_NORTH_EAST = GestureMovement.ANGLE_NORTH_EAST;
    /** Angle in radians that represents East (Right).            */
    public static final float ANGLE_EAST       = GestureMovement.ANGLE_EAST;
    /** Angle in radians that represents South-East (Down-Right). */
    public static final float ANGLE_SOUTH_EAST = GestureMovement.ANGLE_SOUTH_EAST;
    /** Angle in radians that represents South (Down).            */
    public static final float ANGLE_SOUTH      = GestureMovement.ANGLE_SOUTH;
    /** Angle in radians that represents South-West (Down-Left).  */
    public static final float ANGLE_SOUTH_WEST = GestureMovement.ANGLE_SOUTH_WEST;
    /** Angle in radians that represents West (Left).             */
    public static final float ANGLE_WEST       = GestureMovement.ANGLE_WEST;
    /** Angle in radians that represents North-West (Up-Left).    */
    public static final float ANGLE_NORTH_WEST = GestureMovement.ANGLE_NORTH_WEST;
    
    
    
    
    
    /** Private as all methods are static. */
    private GestureFactory() { /* NOOP */ }
    
    
    
    
    
    /**
     * Returns a correctly constructed <code>Gesture</code> using the passed in 
     * parameters.
     * <p>
     * Please see {@link #createGesture(String, float, float, int, float[])}
     * for a more complete description of this method and example usage.
     * </p>
     * 
     * @param  name The name of the <code>Gesture</code>.
     * @param  segments The number of segments to divide the circle into.  This
     *         is used to work out how much the angle parameter should be
     *         'padded'.
     * @param  angles The angles that will be used to create the
     *         <code>GestureMovements</code>.  A <code>GestureMovements</code>
     *         will be created for each angle passed in and they will be added
     *         to the <code>Gesture</code> in the order they are passed.
     * @throws java.lang.IllegalArgumentException For the same reasons as the
     *         standard <code>GestureMovements</code> constructor and if 
     *         segments is less than or equal to zero.
     * @return The <code>Gesture</code>.
     */
    public static Gesture createGesture(String name, int segments, float ... angles) 
        throws IllegalArgumentException
    {
        return createGesture(name, 0f, Integer.MAX_VALUE, segments, angles);
    }
    
    /**
     * Returns a correctly constructed <code>Gesture</code> using the passed in 
     * parameters.
     * <p>
     * The name argument is the same as in the standard <code>Gesture</code>
     * constructor.  The length arguments are the same as in the standard
     * <code>GestureMovement</code> constructor.  The segments parameter is used 
     * to pad out each angle value by a given amount.  It pads out the angles by
     * dividing a full circle (2pi radians) by the number passed then padding
     * each angle out by half this amount on each side.
     * </p><p>
     * Example:
     * <pre>
     * Up = createGesture("Up", 0f, 100f, 8, ANGLE_NORTH);
     * Down = createGesture("Down", 0f, 100f, 8, ANGLE_SOUTH);
     *
     * UpDown = createGesture("Up then Down", 0f, 100f, 8, ANGLE_NORTH, ANGLE_SOUTH);
     * etc
     * </pre>
     * </p>
     * 
     * @param  name The name of the <code>Gesture</code>.
     * @param  minLength The minimum length of each <code>GestureMovement</code>.
     * @param  maxLength The maximum length of each <code>GestureMovement</code>.
     * @param  segments The number of segments to divide the circle into.  This
     *         is used to work out how much the angle parameter should be
     *         'padded'.
     * @param  angles The angles that will be used to create the
     *         <code>GestureMovements</code>.  A <code>GestureMovements</code>
     *         will be created for each angle passed in and they will be added
     *         to the <code>Gesture</code> in the order they are passed.
     * @throws java.lang.IllegalArgumentException For the same reasons as the
     *         standard <code>GestureMovements</code> constructor and if 
     *         segments is less than or equal to zero or if angles is
     *         <code>null</code>.
     * @return The <code>Gesture</code>.
     * @see    com.jmg.gesture.GestureMovement
     * @see    #createGestureMovement(float, float, float, int)
     */
    public static Gesture createGesture(String name, float minLength, 
                                        float maxLength, int segments, 
                                        float ... angles) 
        throws IllegalArgumentException
    {
        if (null == angles) {
            throw new IllegalArgumentException("angles was null!");
        }
        
        
        GestureMovement[] movements = new GestureMovement[angles.length];
        
        for (int i = 0; i < angles.length; i++) {
            movements[i] = createGestureMovement(minLength, maxLength, angles[i], segments);
        }
        
        
        return new Gesture(name, movements);
    }
    
    /**
     * Returns a correctly constructed <code>GestureMovement</code> using the 
     * passed in parameters.
     * <p>
     * Please see {@link #createGestureMovement(float, float, float, int)}
     * for a more complete description of this method and example usage.
     * </p>
     * 
     * @param  angle The angle you wish the movement to be.  This angle will
     *         automatically get 'padded' out by using the segments parameter
     *         so that it can be passed as the minimum and maximum angles.
     * @param  segments The number of segments to divide the circle into.  This
     *         is used to work out how much the angle parameter should be
     *         'padded'.
     * @throws java.lang.IllegalArgumentException For the same reasons as the
     *         standard <code>GestureMovements</code> constructor and if 
     *         segments is less than or equal to zero.
     * @return The <code>GestureMovement</code>.
     */
    public static GestureMovement createGestureMovement(float angle, int segments) 
        throws IllegalArgumentException
    {
        return createGestureMovement(0f, Integer.MAX_VALUE, angle, segments);
    }
    
    /**
     * Returns a correctly constructed <code>GestureMovement</code> using the 
     * passed in parameters.
     * <p>
     * For example, say you wanted all your gestures to use just the four
     * principle compass directions (North, South, East and West) you would
     * call this method as follows:
     * <pre>
     * North = createGestureMovement(minLength, maxLength, ANGLE_NORTH, 4);
     * South = createGestureMovement(minLength, maxLength, ANGLE_SOUTH, 4);
     * etc
     * </pre>
     * If you wanted to use the eight principle compass directions (Above
     * directions + North-East, South-East, etc) then you would call this
     * method as follows:
     * <pre>
     * North = createGestureMovement(minLength, maxLength, ANGLE_NORTH, 8);
     * NorthEast = createGestureMovement(minLength, maxLength, ANGLE_NORTH_EAST, 8);
     * etc
     * </pre>
     * Basically however many directions you wish to use in total for your
     * gestures is passed in as the last parameter.
     * </p>
     * 
     * @param  minLength Same as in the standard constructor.
     * @param  maxLength Same as in the standard constructor.
     * @param  angle The angle you wish the movement to be.  This angle will
     *         automatically get 'padded' out by using the segments parameter
     *         so that it can be passed as the minimum and maximum angles.
     * @param  segments The number of segments to divide the circle into.  This
     *         is used to work out how much the angle parameter should be
     *         'padded'.
     * @throws java.lang.IllegalArgumentException For the same reasons as the
     *         standard <code>GestureMovements</code> constructor and if 
     *         segments is less than or equal to zero.
     * @return The <code>GestureMovement</code>.
     * @see    com.jmg.gesture.GestureMovement
     */
    public static GestureMovement createGestureMovement(float minLength, 
                                                        float maxLength, 
                                                        float angle, 
                                                        int segments) 
        throws IllegalArgumentException
    {
        return new GestureMovement(
                minLength, maxLength,
                getShiftedMinimumAngle(angle, getAngleDividedBySegments(segments * 2)),
                getShiftedMaximumAngle(angle, getAngleDividedBySegments(segments * 2)));
    }
    
    /**
     * Returns the angle if a circle was divided equally into the number of 
     * segments given by the passed parameter 'segments'.
     *
     * @param  segments The number of segments to divide a circle into.
     * @throws java.lang.IllegalArgumentException If a value less than or equal
     *         to zero is passed.
     * @return The angle of each segment.
     */
    public static float getAngleDividedBySegments(int segments) 
        throws IllegalArgumentException
    {
        if (segments <= 0) {
            throw new IllegalArgumentException("segments must be > 0! " + segments);
        }
        
        
        return ((float) Math.PI * 2f) / segments;
    }
    
    /**
     * Shifts the given angle by the given shift amount so
     * it can be used as a minimum angle value.  The angle passed in will be
     * converted to the range:
     * <pre>
     * -{@link java.lang.Math#PI pi} &lt;= angle &lt;= {@link java.lang.Math#PI pi}
     * </pre>
     * Also the returned value will be converted to this range.
     * 
     * @param  angle The angle to shift.
     * @param  shift The angle to shift by.
     * @return The angle correctly shifted by the shift amount.
     */
    public static float getShiftedMinimumAngle(float angle, float shift) {
        if (angle > ANGLE_WEST || angle < -ANGLE_WEST) {
            angle %= ANGLE_WEST;
        }
        
        float ret = (angle - shift) + (Math.signum(shift) * Float.MIN_VALUE);
        
        if (ret > ANGLE_WEST) {
           ret = -ANGLE_WEST + (ret - ANGLE_WEST);
        } else if (ret < -ANGLE_WEST) {
           ret = ANGLE_WEST - (ret + ANGLE_WEST);
        }
        
        return ret;
    }
    
    /**
     * Shifts the given angle by the given shift amount so
     * it can be used as a maximum angle value.  The angle passed in will be
     * converted to the range:
     * <pre>
     * -{@link java.lang.Math#PI pi} &lt;= angle &lt;= {@link java.lang.Math#PI pi}
     * </pre>
     * Also the returned value will be converted to this range.
     * 
     * @param  angle The angle to shift.
     * @param  shift The angle to shift by.
     * @return The angle correctly shifted by the shift amount.
     */
    public static float getShiftedMaximumAngle(float angle, float shift) {
        return getShiftedMinimumAngle(angle, -shift);
    }
    
}
